package cen5501c.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import cen5501c.common.FileContentCmd;
import cen5501c.common.FileListCmd;
import cen5501c.common.GetFileCmd;
import cen5501c.common.P2PException;
import cen5501c.common.FileListCmd.FileListLine;
import cen5501c.utilities.MD5;

public class ServerFileManager {
	private static String path="FileStorage/";
	private static Map<String, FileInfo> fileInfos=null;
	static{
		fileInfos=new HashMap<String, FileInfo>();
		File directory=new File(path);
		if(!directory.exists())directory.mkdir();
		File[] files=directory.listFiles();
		for(File f:files){
			if(!f.isDirectory()){
				String hash=MD5.md5code(f);
//				System.out.println(f.toString());
				FileInfo fi=new ServerFileManager.FileInfo(f.getName(),f.length(),hash,new Date(f.lastModified()));
				fileInfos.put(f.getName(), fi);
			}
		}
	}
	
	public static FileContentCmd getFile(GetFileCmd gfc) throws P2PException,Exception{
		if(gfc==null)return null;
		FileInfo fi=fileInfos.get(gfc.getFilename());
		if(fi==null){
			throw new P2PException("No such File.");
		}
		long end;
		try{
			end=Long.parseLong(gfc.getEndOffset());
		}catch(NumberFormatException nfe){	
			end=fi.getLength();
		}
		long start=gfc.getStartOffset();
		//check range here;
		if(!fi.checkRange(start, end)){
			throw new P2PException("request offset out of range.");
		}
		long contentLen=end-start;
		File f=new File(path+gfc.getFilename());
		FileInputStream fis=null;
		byte[] buffer=new byte[1024];
		StringBuilder sb=new StringBuilder();
		sun.misc.BASE64Encoder en = new sun.misc.BASE64Encoder();
		try{
			fis=new FileInputStream(f);
			fis.skip(start);
			while(contentLen>=1024){
				fis.read(buffer);
				sb.append(en.encode(buffer));
				contentLen-=1024;
			}
			fis.read(buffer);
			//sb.append(new String(buffer,0,(int)contentLen));
			byte[] b1 = new byte[(int)contentLen];
			System.arraycopy(buffer, 0, b1, 0, (int)contentLen);
			sb.append(en.encode(b1));
		}catch(IOException ie){
			throw new P2PException("An IO Exception occured.");
		}finally{
			fis.close();
		}
		List<String> ls=new ArrayList<String>(6);
		ls.add(fi.getFileName());
		ls.add(fi.getHash());
		ls.add(String.valueOf(start));
		ls.add(String.valueOf(end));
		String completed="false";
		if(end==fi.getLength())completed="true";
		ls.add(completed);
		ls.add(sb.toString());
		return new FileContentCmd(ls);
	}
	
	public static FileListCmd getList(){
		int numFile=fileInfos.size();
		Date responseDate=new Date();
		List<FileListLine> lfll=new ArrayList<FileListLine>();
		Iterator<Map.Entry<String, FileInfo>> itm=fileInfos.entrySet().iterator();
		while(itm.hasNext()){
			Map.Entry<String, FileInfo> entry=itm.next();
			FileInfo fi=entry.getValue();
			lfll.add(new FileListCmd.FileListLine(fi.getFileName(),fi.getHash(),fi.getCreatedDate(),0,(int)fi.getLength()));
		}
		return new FileListCmd(numFile,responseDate,lfll);
	}
	
	static class FileInfo{
		private String fileName=null;
		private long length;
		private String hash=null;
		private Date createdDate=null;
		
		public FileInfo(String fileName,long length, String hash,Date createdDate){
			this.fileName=fileName;
			this.length=length;
			this.hash=hash;
			this.createdDate=createdDate;
		}
		//start inclusive, end exclusive
		public boolean checkRange(long start, long end){
			if(start<0 || end < 0) return false;
			if(start>=end&&end!=-1)return false;
			if(end>length)return false;
			return true;
		}

		public String getFileName() {
			return fileName;
		}

		public void setFileName(String fileName) {
			this.fileName = fileName;
		}

		public String getHash() {
			return hash;
		}

		public void setHash(String hash) {
			this.hash = hash;
		}

		public long getLength() {
			return length;
		}

		public void setLength(long length) {
			this.length = length;
		}
		public Date getCreatedDate() {
			return createdDate;
		}
		public void setCreatedDate(Date createdDate) {
			this.createdDate = createdDate;
		}
	}
}
